New database sharding methods, improved Active Record Migration Docs, caching improvements for ActiveStorage and more! | This Week in Rails
https://world.hey.com/this.week.in.rails/new-database-sharding-methods-improved-active-record-migration-docs-caching-improvements-for-8265049e
Add .shard_keys , .sharded? , & .connected_to_all_shards methods to AR Models by HeyNonster · Pull Request #51009 · rails/rails · GitHub
ActiveRecordに関する変更です
まずはActiveRecordにおける水平シャーディングについて簡単に触れます
https://railsguides.jp/active_record_multiple_databases.html#水平シャーディング
水平シャーディングとは、データベースを分割して各データベースサーバーの行数を減らしながら「シャード(shard)」全体で同じスキーマを維持することです。
(中略)
モデルは shardsキーを介してconnects_toAPIに接続されます。
例として次のような実装を紹介します
code:rb
class ShardedBase < ActiveRecord::Base
self.abstract_class = true
connects_to shards: {
shard_one: { writing: :shard_one },
shard_two: { writing: :shard_two }
}
end
code:rb
class User < ShardedBase
# モデルの定義
end
code:rb
class UsersController < ApplicationController
before_action :set_shard
def create
@user = User.create(user_params)
# その他のアクション
end
private
def set_shard
shard = (params:user:company_id.to_i % 2 == 0) ? :shard_one : :shard_two
User.connected_to(shard: shard) do
yield
end
end
def user_params
params.require(:user).permit(:name, :email)
end
end
company_id が偶数か奇数かによって接続するシャードを切り替える、という実装になっています
今回のプルリクエストではシャーディングに関して3つのメソッドを追加しています
code:rb
class ShardedBase < ActiveRecord::Base
self.abstract_class = true
connects_to shards: {
shard_one: { writing: :shard_one },
shard_two: { writing: :shard_two }
}
end
class ShardedModel < ShardedBase
end
ShardedModel.shard_keys => :shard_one, :shard_two
ShardedModel.sharded? => true
ShardedBase.connected_to_all_shards { ShardedModel.current_shard } => :shard_one, :shard_two
1つ目は shard_keys です
shardのkeyの一覧を取得するメソッドです
これまではモデルの接続をループする方法以外で、shardのkeyの一覧を取得するのは難しかったそうです
調べてみたところDBレイヤーでのshardの名前を取得する方法はあったみたいですが、ActiveRecordに指定したshardのkeyを取得する方法がなかったみたい?(わからん)
https://github.com/rails/rails/blob/5bec50bc70380bb1e70e8fb0a1654130042b1f16/activerecord/lib/active_record/connection_adapters/pool_manager.rb#L10-L12
2つ目は sharded? です
モデルが複数のシャードに接続し得る場合に true を返すメソッドです
3つ目は connected_to_all_shards です
すべてのシャードに接続してブロック内の処理を実行するメソッドです
Lazily generate assertion failure messages by byroot · Pull Request #52157 · rails/rails · GitHub
ActiveSupportに関する変更です
2週間ほど前(2024-06-07)につくられたプルリクエストに関連するプルリクエストなので、まずはそちらから説明します
そのプルリクエストは Improve error message when passing a proc to assert_difference or assert_changes by richardboehme · Pull Request #52036 · rails/rails · GitHub です
アサーションメソッドのうち assert_difference メソッド、または assert_changes メソッドをつかうメソッドのエラーメッセージの変更が行なわれています
ここで assert_difference メソッドについて話しておきましょう
https://api.rubyonrails.org/classes/ActiveSupport/Testing/Assertions.html#method-i-assert_difference
このメソッドは主に次の3つを受け取ります
第1引数:評価される式
第2引数:期待される変化の量(デフォルトは1)
ブロック:実行されるコード
ブロック内のコードの実行前後で、第1引数に渡した式の評価結果が期待された変化をしているか(変化量が第2引数と一致しているか)をチェックします
たとえばこのようなコードです
code:rb
assert_difference('Article.count', 2) do
post :create, params: { article: {...} }
post :create, params: { article: {...} }
end
第1引数に渡した Article.count の評価結果が、ブロック内の post :create, ... を2回実行するによって、第2引数に渡した 2 だけ変化するか?、というテストですね
先程の例では第1引数に文字列を渡しましたが、Procオブジェクトを渡すこともできます
code:rb
assert_difference(-> { Article.count }, 2) do
post :create, params: { article: {...} }
post :create, params: { article: {...} }
end
テストの検証内容は先ほどとまったく同じです
さて、プルリクエストの話に戻しましょう
assert_difference メソッドのエラーメッセージを変更した、という話でしたね
先ほど例を示した通り、assert_difference メソッドの第1引数にはStringもProcオブジェクトも渡せます
プルリクエストの筆者は、StringよりもProcオブジェクトを渡すほうが良いという考えを持っています
なぜなら、Procオブジェクトであればproc内のコードにlinterを適用できるしRubyコードとしての解析ができるためです(明記されていませんが、実装エラーに気づきやすくなるなどの利点もあるでしょう)
ただし、Procオブジェクトを渡した場合、アサーション失敗時のエラーメッセージがわかりにくい、という問題点がありました
具体的にはエラーメッセージがProcオブジェクトのinspectを表示するようになっていました
code:サンプルテスト.rb
test "see proc output" do
assert_difference -> { 1 } do
end
end
code:エラーメッセージ(変更前)
#<Proc:0x000074357846eae8 /home/richard/my-test-app/test/models/test.rb:21 (lambda)> didn't change by 1, but by 0.
Expected: 2
Actual: 1
これでは何がエラーになったのかわかりにくいですよね
そこでこのプルリクエストでは RubyVM::AbstractSyntaxTree APIを利用して、procのソースコードを出力することで、エラーメッセージがわかりやすくなるように変更をしています
code:エラーメッセージ(変更後)
"-> { 1 }" didn't change by 1, but by 0.
Expected: 2
Actual: 1
実装の中身がわかるので、わかりやすいですね
さて、ここまでで2週間ほど前に出されたプルリクエストの詳細が把握できました
では今回のプルリクエストではどのような変更がされたのかというと、端的に言うとパフォーマンスの改善です
2週間前のプルリクエストで、ASTをつかってProcオブジェクトのソースコードを出力するようになりました
これは変更前の処理(Procオブジェクトのinspectを表示する処理)に比べたら処理コストが掛るようになります
そこで、今回のプルリクエストではエラーメッセージを遅延呼び出し可能なProcオブジェクトとして実装し直しています